/**
 * Copyright 2020 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.dbfly.mybatis.builder.method;

import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.List;

import org.apache.ibatis.annotations.Param;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.jianggujin.dbfly.mybatis.entity.JEntityColumn;
import com.jianggujin.dbfly.mybatis.util.JElementBuilder;
import com.jianggujin.dbfly.util.JDBFlyException;
import com.jianggujin.dbfly.util.JStringUtils;

public enum JType {

    IS_NOT_NULL(0, "IsNotNull", "NotNull") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            return JStringUtils.format("{} is not null", column.getColumn());
        }
    },
    IS_NULL(0, "IsNull", "Null") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            return JStringUtils.format("{} is null", column.getColumn());
        }
    },
    NOT_EQUALS("IsNot", "Not", "NotEquals", "Ne") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            return new Object[] { JStringUtils.format("{} <> ", column.getColumn()),
                    build(parameters, index, document, column) };
        }
    },
    EQUALS("Is", "Equals", "Eq") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            return new Object[] { JStringUtils.format("{} = ", column.getColumn()),
                    build(parameters, index, document, column) };
        }
    },
    GREATER_THAN("IsGreaterThan", "GreaterThan", "Gt") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            return new Object[] { JStringUtils.format("{} > ", column.getColumn()),
                    build(parameters, index, document, column) };
        }
    },
    GREATER_THAN_EQUAL("IsGreaterThanEqual", "GreaterThanEqual", "Ge") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            return new Object[] { JStringUtils.format("{} >= ", column.getColumn()),
                    build(parameters, index, document, column) };
        }
    },
    LESS_THAN("IsLessThan", "LessThan", "Lt") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            return new Object[] { JStringUtils.format("{} < ", column.getColumn()),
                    build(parameters, index, document, column) };
        }
    },
    LESS_THAN_EQUAL("IsLessThanEqual", "LessThanEqual", "Le") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            return new Object[] { JStringUtils.format("{} <= ", column.getColumn()),
                    build(parameters, index, document, column) };
        }
    },
    BEFORE("IsBefore", "Before") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            return new Object[] { JStringUtils.format("{} < ", column.getColumn()),
                    build(parameters, index, document, column) };
        }
    },
    BEFORE_EQUAL("IsBeforeEqual", "BeforeEqual") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            return new Object[] { JStringUtils.format("{} <= ", column.getColumn()),
                    build(parameters, index, document, column) };
        }
    },
    AFTER("IsAfter", "After") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            return new Object[] { JStringUtils.format("{} > ", column.getColumn()),
                    build(parameters, index, document, column) };
        }
    },
    AFTER_EQUAL("IsAfterEqual", "AfterEqual") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            return new Object[] { JStringUtils.format("{} >= ", column.getColumn()),
                    build(parameters, index, document, column) };
        }
    },
    NOT_IN("IsNotIn", "NotIn") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            Element foreachElement = JElementBuilder.buildForeachElement(document, "(", ")",
                    this.buildList(parameters, index, document, column).toString(), "listItem", ",");
            JElementBuilder.appendChild(document, foreachElement, "#{listItem}");
            return new Object[] { JStringUtils.format("{} not in ", column.getColumn()), foreachElement };
        }

        private CharSequence buildList(Parameter[] parameters, int index, Document document, JEntityColumn column) {
            int len = parameters.length;
            if (len > 1) {
                Param param = parameters[index].getAnnotation(Param.class);
                if (param != null) {
                    return param.value();
                } else {
                    // sql.append("arg").append(index);// =>param[1...]
                    return JStringUtils.format("param{}", index + 1);// =>param[1...]
                }
            } else {
                // 参数只有一个的时候需要注意
                Param param = parameters[index].getAnnotation(Param.class);
                if (param != null) {
                    return param.value();
                }
                Class<?> pType = parameters[0].getType();
                if (List.class.isAssignableFrom(pType)) {
                    return "list";
                }
                if (pType.isArray()) {
                    return "array";
                }
                throw new JDBFlyException("IN类型且为单参数必须为List、Array或添加@Param注解");
            }
        }
    },
    IN("IsIn", "In") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            Element foreachElement = JElementBuilder.buildForeachElement(document, "(", ")",
                    this.buildList(parameters, index, document, column).toString(), "listItem", ",");
            JElementBuilder.appendChild(document, foreachElement, "#{listItem}");
            return new Object[] { JStringUtils.format("{} in ", column.getColumn()), foreachElement };
        }

        private CharSequence buildList(Parameter[] parameters, int index, Document document, JEntityColumn column) {
            int len = parameters.length;
            if (len > 1) {
                Param param = parameters[index].getAnnotation(Param.class);
                if (param != null) {
                    return param.value();
                } else {
                    // sql.append("arg").append(index);// =>param[1...]
                    return JStringUtils.format("param{}", index + 1);// =>param[1...]
                }
            } else {
                // 参数只有一个的时候需要注意
                Param param = parameters[index].getAnnotation(Param.class);
                if (param != null) {
                    return param.value();
                }
                Class<?> pType = parameters[0].getType();
                if (List.class.isAssignableFrom(pType)) {
                    return "list";
                }
                if (pType.isArray()) {
                    return "array";
                }
                throw new JDBFlyException("IN类型且为单参数必须为List、Array或添加@Param注解");
            }
        }
    },
    NOT_BETWEEN(2, "IsNotBetween", "NotBetween") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            return new Object[] { JStringUtils.format("{} not between ", column.getColumn()),
                    build(parameters, index, document, column), " and ",
                    build(parameters, index + 1, document, column) };
        }
    },
    BETWEEN(2, "IsBetween", "Between") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            return new Object[] { JStringUtils.format("{} between ", column.getColumn()),
                    build(parameters, index, document, column), " and ",
                    build(parameters, index + 1, document, column) };
        }
    },
    NOT_LIKE("IsNotLike", "NotLike") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            return new Object[] { JStringUtils.format("{} not like ", column.getColumn()),
                    build(parameters, index, document, column) };
        }
    },
    LIKE("IsLike", "Like") {
        @Override
        public Object build(Parameter[] parameters, int index, JEntityColumn column, Document document) {
            return new Object[] { JStringUtils.format("{} like ", column.getColumn()),
                    build(parameters, index, document, column) };
        }
    };

    /**
     * 关键词
     */
    private final String[] keywords;
    /**
     * 参数数量
     */
    private final int numberOfArguments;

    private JType(int numberOfArguments, String... keywords) {
        this.numberOfArguments = numberOfArguments;
        this.keywords = keywords;
    }

    private JType(String... keywords) {
        this(1, keywords);
    }

    /**
     * 从原始属性创建
     * 
     * @param rawProperty
     * @return
     */
    public static JType fromProperty(String rawProperty) {
        for (JType type : JType.values()) {
            if (type.supports(rawProperty)) {
                return type;
            }
        }
        return EQUALS;
    }

    /**
     * 判断是否支持
     * 
     * @param property
     * @return
     */
    protected boolean supports(String property) {
        for (String keyword : keywords) {
            if (property.endsWith(keyword)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获得参数个数
     * 
     * @return
     */
    public int getNumberOfArguments() {
        return numberOfArguments;
    }

    /**
     * 抽取实际属性
     * 
     * @param part
     * @return
     */
    public String extractProperty(String part) {
        String candidate = JStringUtils.firstCharToLowerCase(part);

        for (String keyword : keywords) {
            if (candidate.endsWith(keyword)) {
                return candidate.substring(0, candidate.length() - keyword.length());
            }
        }
        return candidate;
    }

    @Override
    public String toString() {
        return String.format("%s (%s): %s", name(), getNumberOfArguments(), Arrays.toString(keywords));
    }

    /**
     * 构建子元素
     * 
     * @param parameters
     * @param index
     * @param column
     * @param document
     * @return 返回可用元素，元素类型可以是{@link CharSequence}、null、{@link Node}、Object[]
     */
    public abstract Object build(Parameter[] parameters, int index, JEntityColumn column, Document document);

    protected static StringBuilder build(String property, JEntityColumn column) {
        StringBuilder builder = new StringBuilder();
        builder.append("#{");
        builder.append(property);
        // 如果 null 被当作值来传递，对于所有可能为空的列，JDBC Type 是需要的
        if (column.getJdbcType() != null) {
            builder.append(", jdbcType=");
            builder.append(column.getJdbcType().toString());
        }
        // 为了以后定制类型处理方式，你也可以指定一个特殊的类型处理器类，例如枚举
        if (column.getTypeHandler() != null) {
            builder.append(", typeHandler=");
            builder.append(column.getTypeHandler().getCanonicalName());
        }
        // 3.4.0 以前的 mybatis 无法获取父类中泛型的 javaType，所以如果使用低版本，就需要设置 useJavaType = true
        if (column.isUseJavaType() && !column.getJavaType().isArray()) {
            builder.append(", javaType=");
            builder.append(column.getJavaType().getCanonicalName());
        }
        builder.append("}");
        return builder;
    }

    protected static StringBuilder build(Parameter[] parameters, int index, Document document, JEntityColumn column) {
        int len = parameters.length;
        if (len > 1) {
            Param param = parameters[index].getAnnotation(Param.class);
            if (param != null) {
                return build(param.value(), column);
            } else {
                // sql.append("arg").append(index);// =>param[1...]
                return build(JStringUtils.format("param{}", index + 1), column);// =>param[1...]
            }
        } else {
            return build("value", column);
        }
    }
}
